// *************************************************************************************************
//
// MK3D Engine
// --------------------------------------
// Copyright (C) 2007-2011 Zeb (mailto: zebbey@gmail.com)
//
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// *************************************************************************************************

#include "mk_header.h"
#include "mk_tool_rotation.h"
#include "mk_engine.h"
#include "mk_error.h"
#include "mk_painter.h"
#include "mk_camera.h"
#include "mk_math.h"
#include "mk_render_window.h"

namespace MK3DE
{
	const int __CIRCLE_SEG = 36;
	const int __CIRCLE_COLLISION_SEG = 8;
	const float __JUDGE_RANGE_SQ = 25.0f;

	CToolRotation* CToolRotation::Create()
	{
		return new CToolRotation();
	}

	void CToolRotation::Destroy(CToolRotation* tool)
	{
		delete tool;
	}

	CToolRotation::CToolRotation()
	{
		_bEnable = TRUE;
		_x = 0;
		_y = 0;
		_z = 0;
		_yaw = 0;
		_pitch = 0;
		_roll = 0;
		_radius = 50.0f;
		_currentAxis = AXIS_NONE;
		_bSelected = FALSE;
	}

	CToolRotation::~CToolRotation()
	{

	}

	void CToolRotation::Render()
	{
		if (!_bEnable)
			return;

		CCamera* cam = GetEngine()->GetCurrentCamera();
		CRenderWindow* win = GetEngine()->GetCurrentRenderWindow();
		ASSERT(win);
		D3DXVECTOR3 center3D(_x, _y, _z);
		D3DXVECTOR2 center;
		cam->ProjectPoint(center, center3D, win->GetWidth(), win->GetHeight());
		D3DXMATRIX matView = cam->GetViewMatrix();
		D3DXPLANE plane;
		D3DXPlaneFromPointNormal(&plane, &center3D, &D3DXVECTOR3(matView._13, matView._23, matView._33));

		float factor = 0.12f / _radius * win->GetHeight();
		CPainter* painter = GetEngine()->GetPainter();
		float delta = MK_DOUBLE_PI / (float)__CIRCLE_SEG;
		float x = _radius;
		float z = 0;
		int i = 1;
		while (i <= __CIRCLE_SEG)
		{
			float x1 = cosf(delta * i) * _radius;
			float z1 = sinf(delta * i) * _radius;

			painter->DrawLine2D(D3DXVECTOR2(x * factor + center.x, z * factor + center.y), D3DXVECTOR2(x1 * factor + center.x, z1 * factor + center.y), 0xff404040, 0xff404040);

			{
				D3DXVECTOR3 p1(x + _x, _y, z + _z);
				D3DXVECTOR3 p2(x1 + _x, _y, z1 + _z);
				float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
				float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
				if (dis1 < 0 && dis2 < 0)
				{    
					if (_currentAxis == AXIS_Y)
						painter->DrawLine3D(D3DXVECTOR3(x + _x, _y, z + _z), D3DXVECTOR3(x1 + _x, _y, z1 + _z), 0xffffff00, 0xffffff00);
					else
						painter->DrawLine3D(D3DXVECTOR3(x + _x, _y, z + _z), D3DXVECTOR3(x1 + _x, _y, z1 + _z), 0xff00ff00, 0xff00ff00);
				}
			}

			{
				D3DXVECTOR3 p1(_x, x + _y, z + _z);
				D3DXVECTOR3 p2(_x, x1 + _y, z1 + _z);
				float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
				float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
				if (dis1 < 0 && dis2 < 0)
				{
					if (_currentAxis == AXIS_X)
						painter->DrawLine3D(D3DXVECTOR3(_x, x + _y, z + _z), D3DXVECTOR3(_x, x1 + _y, z1 + _z), 0xffffff00, 0xffffff00);
					else
						painter->DrawLine3D(D3DXVECTOR3(_x, x + _y, z + _z), D3DXVECTOR3(_x, x1 + _y, z1 + _z), 0xffff0000, 0xffff0000);
				}
			}


			{
				D3DXVECTOR3 p1(x + _x, z + _y, _z);
				D3DXVECTOR3 p2(x1 + _x, z1 + _y, _z);
				float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
				float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
				if (dis1 < 0 && dis2 < 0)
				{
					if (_currentAxis == AXIS_Z)
						painter->DrawLine3D(D3DXVECTOR3(x + _x, z + _y, _z), D3DXVECTOR3(x1 + _x, z1 + _y, _z), 0xffffff00, 0xffffff00);
					else
						painter->DrawLine3D(D3DXVECTOR3(x + _x, z + _y, _z), D3DXVECTOR3(x1 + _x, z1 + _y, _z), 0xff0000ff, 0xff0000ff);
				}
			}

			x = x1;
			z = z1;
			++i;
		}
	}

	void CToolRotation::Update()
	{
		if (!_bEnable)
			return;

		D3DXVECTOR3 pos(_x, _y, _z);
		CCamera* cam = GetEngine()->GetCurrentCamera();
		float n = cam->GetViewNear();
		float f = cam->GetViewFar();
		D3DXVec3TransformCoord(&pos, &pos, &cam->GetViewMatrix());
		_radius = pos.z * 0.1f;
	}

	void CToolRotation::SetPosition(float x, float y, float z)
	{
		_x = x;
		_y = y;
		_z = z;

		_yaw = 0;
		_pitch = 0;
		_roll = 0;

		_bSelected = FALSE;
		_currentAxis = AXIS_NONE;
	}

	void CToolRotation::GetRotation(/* out */ float& yaw, /* out */ float& pitch, /* out */ float& roll)
	{
		yaw = _yaw;
		pitch = _pitch;
		roll = _roll;
	}

	BOOL CToolRotation::OnMouseLeftDown(float x, float y)
	{
		if (!_bEnable)
			return FALSE;

		if (_currentAxis == AXIS_NONE)
			return FALSE;

		_lastPos.x = x;
		_lastPos.y = y;
		_bSelected = TRUE;

		return TRUE;
	}

	BOOL CToolRotation::OnMouseMove(float x, float y)
	{
		if (!_bEnable)
			return FALSE;

		CCamera* cam = GetEngine()->GetCurrentCamera();
		CRenderWindow* win = GetEngine()->GetCurrentRenderWindow();
		ASSERT(win);
		D3DXVECTOR3 center3D(_x, _y, _z);

		if (_bSelected)
		{
			assert(_currentAxis != AXIS_NONE);

			D3DXVECTOR2 pos1, pos2;
			cam->ProjectPoint(pos1, center3D, win->GetWidth(), win->GetHeight());
			if (_currentAxis == AXIS_X)
				cam->ProjectPoint(pos2, D3DXVECTOR3(_x, _y + _radius, _z), win->GetWidth(), win->GetHeight());
			else if (_currentAxis == AXIS_Y)
				cam->ProjectPoint(pos2, D3DXVECTOR3(_x - _radius, _y, _z), win->GetWidth(), win->GetHeight());
			else if (_currentAxis == AXIS_Z)
				cam->ProjectPoint(pos2, D3DXVECTOR3(_x - _radius, _y, _z), win->GetWidth(), win->GetHeight());

			D3DXVECTOR2 A = pos2 - pos1;
			D3DXVECTOR2 B(x - _lastPos.x, y - _lastPos.y);
			float lenA = D3DXVec2Length(&A);
			float lenB = D3DXVec2Length(&B);
			D3DXVec2Normalize(&A, &A);
			D3DXVec2Normalize(&B, &B);
			float dot = D3DXVec2Dot(&A, &B);
			float dis = dot * lenB * _radius / lenA;

			if (_currentAxis == AXIS_X)
				_pitch += dis * 0.01f;
			else if (_currentAxis == AXIS_Y)
				_yaw += dis * 0.01f;
			else if (_currentAxis == AXIS_Z)
				_roll += dis * 0.01f;

			_lastPos.x = x;
			_lastPos.y = y;
		}
		else
		{
			D3DXMATRIX matView = cam->GetViewMatrix();
			D3DXPLANE plane;
			D3DXPlaneFromPointNormal(&plane, &center3D, &D3DXVECTOR3(matView._13, matView._23, matView._33));

			CPainter* painter = GetEngine()->GetPainter();
			float delta = MK_DOUBLE_PI / (float)__CIRCLE_COLLISION_SEG;
			float x0 = _radius;
			float z0 = 0;
			int i = 1;
			while (i <= __CIRCLE_COLLISION_SEG)
			{
				float x1 = cosf(delta * i) * _radius;
				float z1 = sinf(delta * i) * _radius;

				{
					D3DXVECTOR3 p1(x0 + _x, _y, z0 + _z);
					D3DXVECTOR3 p2(x1 + _x, _y, z1 + _z);
					float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
					float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
					if (dis1 < 0 && dis2 < 0)
					{    
						D3DXVECTOR2 proj1, proj2;
						cam->ProjectPoint(proj1, p1, win->GetWidth(), win->GetHeight());
						cam->ProjectPoint(proj2, p2, win->GetWidth(), win->GetHeight());
						if (IsPointNearSegment(proj1, proj2, x, y, __JUDGE_RANGE_SQ))
						{
							_currentAxis = AXIS_Y;
							return TRUE;
						}
					}
				}

				{
					D3DXVECTOR3 p1(_x, x0 + _y, z0 + _z);
					D3DXVECTOR3 p2(_x, x1 + _y, z1 + _z);
					float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
					float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
					if (dis1 < 0 && dis2 < 0)
					{
						D3DXVECTOR2 proj1, proj2;
						cam->ProjectPoint(proj1, p1, win->GetWidth(), win->GetHeight());
						cam->ProjectPoint(proj2, p2, win->GetWidth(), win->GetHeight());
						if (IsPointNearSegment(proj1, proj2, x, y, __JUDGE_RANGE_SQ))
						{
							_currentAxis = AXIS_X;
							return TRUE;
						}
					}
				}


				{
					D3DXVECTOR3 p1(x0 + _x, z0 + _y, _z);
					D3DXVECTOR3 p2(x1 + _x, z1 + _y, _z);
					float dis1 = p1.x * plane.a + p1.y * plane.b + p1.z * plane.c + plane.d;
					float dis2 = p2.x * plane.a + p2.y * plane.b + p2.z * plane.c + plane.d;
					if (dis1 < 0 && dis2 < 0)
					{
						D3DXVECTOR2 proj1, proj2;
						cam->ProjectPoint(proj1, p1, win->GetWidth(), win->GetHeight());
						cam->ProjectPoint(proj2, p2, win->GetWidth(), win->GetHeight());
						if (IsPointNearSegment(proj1, proj2, x, y, __JUDGE_RANGE_SQ))
						{
							_currentAxis = AXIS_Z;
							return TRUE;
						}
					}
				}

				x0 = x1;
				z0 = z1;
				++i;
			}

			_currentAxis = AXIS_NONE;
		}

		return TRUE;
	}

	BOOL CToolRotation::OnMouseLeftUp(float x, float y)
	{
		if (!_bEnable)
			return FALSE;

		_bSelected = FALSE;

		return TRUE;
	}
}